/*
 * @Author: zzd993
 * @Date: 2023-02-11 14:11:16
 * @LastEditors: zzd993
 * @LastEditTime: 2023-02-17 16:09:55
 * @FilePath: \手写vue2源码\src\observe\watcher.js
 * Copyright (c) 2023 by ZZD, All Rights Reserved. 
 */

import Dep, { popTarget, pushTarget } from "./dep"

let id = 0

// 当我们创建渲染watcher的时候会把当前的渲染watcher放到Dep.target上
// 调用_render()会取值，走到get上，先把当前的渲染watcher放到全局上，之后去取值
// 取值时可以让当前这个属性的dep记住这个watcher


class Watcher {  // 不同组件有不同的watcher，目前只有一个渲染根实例的
    constructor(vm, exprOrFn, options, cb) {
        this.id = id++
        this.renderWatcher = options  // 是一个渲染watcher
        
        if(typeof exprOrFn === 'string') {
            this.getter = function() {
                return vm[exprOrFn]
            }
        } else {
            this.getter = exprOrFn  // getter意味着调用这个函数可以发送取值操作
        }
        this.deps = []  // watcher记住dep是为了后续实现计算属性和组件卸载时清除相应的响应式数据等工作
        this.depsId = new Set()
        this.get()
        this.cb = cb
        this.lazy = options.lazy
        this.dirty = this.lazy  // 缓存值
        this.vm = vm
        this.value = this.lazy ? undefined : this.get()
        this.user = options.user
    }
    addDep(dep) {  // 一个组件对应多个属性，重复的属性也不用记录
        let id = dep.id
        if(!this.depsId.has(id)) {
            this.deps.push(dep)
            this.depsId.add(id)
            dep.addSub(this)  // watcher已经记住dep且去重了，此时让dep也记住watcher
        }
    }
    evaluate() {
        this.value = this.get()  // 获取到用户函数的返回值并且标识为脏
        this.dirty = false
    }
    get() {
        pushTarget()
        let value = this.getter.call(this.vm)  // 会去vm上取值
        popTarget()
        return value
    }
    depend() {
        let l = this.deps.length
        while(l--) {
            this.deps[l].depend()
        }
    }
    update() {
        // this.get()  // 重新渲染
        if(this.lazy) {
            // 如果是计算属性，依赖的值变化了就标识计算属性是脏值了
            this.dirty = true
        } else {
            queueWatcher(this)  // 把当前的watcher暂存起来
        }
        
    }
    run() {
        let oldValue = this.value
        let newValue = this.get()    // 渲染的时候用的是新的vm来渲染的
        if(this.user) {
            this.cb.call(this.vm, newValue, oldValue)
        }
    }
}

let queue = []
let has = {}
let pending = false

function flushSchedulerQueue() {
    let flushQueue = queue.slice(0)
    queue = []
    has = {}
    pending = false
    flushQueue.forEach(q => q.run())  // 在刷新的过程中可能还有新的watcher会重新放到queue中
    
}
function queueWatcher(watcher) {
    const id = watcher.id
    if(!has[id]) {
        queue.push(watcher)
        has[id] = true
        // 不管update执行多少次，最终只执行一轮刷新操作
        if(!pending) {  // 第一次刷新之后就进不来了，然后等主线程走完在执行定时器
            nextTick(flushSchedulerQueue)
            pending = true
        }
    }
    
}


let callbacks = []
let waiting = false
function flushCallbacks() {
    let cbs = callbacks.slice(0)
    waiting = false
    callbacks = []
    cbs.forEach(cb => cb())
}


let timeFunc
if(Promise) {
    timeFunc = () => {
        Promise.resolve().then(flushCallbacks)
    }
} else if(MutationObserver) {
    let observer = new MutationObserver(flushCallbacks)
    let textNode = document.createTextNode(1)
    observer.observe(textNode, {
        characterData: true
    })
    timeFunc = () => {
        textNode.textContent = 2
    }
}else if(setImmediate) {
    timeFunc = () => {
        setImmediate(flushCallbacks)
    }
} else {
    timeFunc = () => {
        setTimeout(flushCallbacks)
    }
}
// nextTick 没有直接使用某个api，而是采用优雅降级的方式
// 内部采用的是promise、MutationObserber、setImmediate、setTimeout
export function nextTick(cb) {
    callbacks.push(cb)   // 维护nextTick中的callback方法
    if(!waiting) {
        timeFunc()   // 最后一起刷新
        waiting = true
    }
}


// 需要给每个属性增加一个dep，目的就是收集watcher
// 一个组件对应多个属性，一个属性可能对应一或多个组件。
// 每个属性都有一个dep，一个dep对应n个watcher，n个dep对应一个watcher
export default Watcher